package cn.elves.model.message.server;

import cn.elves.model.jwt.JwtUtil;
import cn.elves.model.message.consts.Constants;
import cn.elves.model.message.entity.Message;
import cn.elves.model.message.server.store.StoreFactoryProvider;
import cn.elves.model.message.util.PushUtils;
import cn.elves.model.message.util.ServerConfigUtils;
import com.alibaba.fastjson.JSON;
import com.corundumstudio.socketio.*;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DataListener;
import com.corundumstudio.socketio.listener.DisconnectListener;
import com.corundumstudio.socketio.listener.ExceptionListener;
import com.corundumstudio.socketio.namespace.Namespace;
import com.corundumstudio.socketio.protocol.Packet;
import com.corundumstudio.socketio.protocol.PacketType;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;

import java.net.SocketAddress;
import java.util.*;

@Slf4j
public class PushServer {

    public static final PushServer pushServer = new PushServer();
    private Namespace pushNamespace;
    private SocketIOServer server;

    private PushServer() {
        Configuration configuration = new Configuration();
        configuration.setStoreFactory(StoreFactoryProvider.getRedissonStoreFactory());
        configuration.setAuthorizationListener(new UserAuthorizationListener());
        configuration.setPort(ServerConfigUtils.instance.getWebSocketPort());
        configuration.setContext(ServerConfigUtils.instance.getWebSocketContext());
        configuration.setOrigin(ServerConfigUtils.instance.getWebSocketOrigin());
        SocketConfig socketConfig = new SocketConfig();
        socketConfig.setReuseAddress(true);
        configuration.setSocketConfig(socketConfig);

        server = new SocketIOServer(configuration);
         server.addNamespace(ServerConfigUtils.instance.getWebSocketContext());
        pushNamespace = (Namespace) server.getNamespace(ServerConfigUtils.instance.getWebSocketContext());
        configuration.setExceptionListener(new ExceptionListener() {
            @Override
            public void onEventException(Exception e, List<Object> list, SocketIOClient socketIOClient) {
                UUID sessionId = socketIOClient.getSessionId();
                log.error("onEventException, sessionId: {}, roomList:{}", sessionId, socketIOClient.get(Constants.ROOM), e);
            }

            @Override
            public void onDisconnectException(Exception e, SocketIOClient socketIOClient) {
                UUID sessionId = socketIOClient.getSessionId();
                log.error("onDisconnectException, sessionId: {}, roomList:{}", sessionId, socketIOClient.get(Constants.ROOM), e);
            }

            @Override
            public void onConnectException(Exception e, SocketIOClient socketIOClient) {
                UUID sessionId = socketIOClient.getSessionId();
                log.info("onConnectException, sessionId: {}, roomList:{}", sessionId, socketIOClient.get(Constants.ROOM), e);
            }

            @Override
            public void onPingException(Exception e, SocketIOClient socketIOClient) {
                try {
                    boolean channelOpen = socketIOClient.isChannelOpen();
                    SocketAddress remoteAddress = socketIOClient.getRemoteAddress();
                    UUID sessionId = socketIOClient.getSessionId();
                    log.error("onPingException, channelOpen:{}, sessionId: {}, remoteAddress:{}, roomList:{}", channelOpen, sessionId, remoteAddress, socketIOClient.get(Constants.ROOM), e);
                } catch (Exception e1) {
                    log.error("", e1);
                }
            }

            @Override
            public boolean exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) throws Exception {
                try {
                    Channel channel = channelHandlerContext.channel();
                    if (channel != null) {
                        log.error("onPingException, channel:{}, isOpen: {}, remoteAddress:{}", channel.id(), channel.isOpen(), channel.remoteAddress());
                    }
                } catch (Exception e) {
                    log.error("exceptionCaught", e);
                }
                return false;
            }
        });

        pushNamespace.addEventListener("send-message", Map.class, new DataListener<Map>() {
            @Override
            public void onData(SocketIOClient socketIOClient, Map map, AckRequest ackRequest) throws Exception {
                JwtUtil.JwtResult userInfo = UserAuthorizationListener.getUserInfo(socketIOClient.getHandshakeData());
                Integer userId = userInfo.getUserId();
                Object content = map.get("content");
                Object type = map.get("type");
                Message message = new Message();

                push(message);
            }
        });
        pushNamespace.addDisconnectListener(new DisconnectListener() {
            @Override
            public void onDisconnect(SocketIOClient client) {
                UUID sessionId = client.getSessionId();
                JwtUtil.JwtResult userInfo = UserAuthorizationListener.getUserInfo(client.getHandshakeData());
                if (userInfo != null) {
                    Set<String> rooms = client.getAllRooms();
                    rooms.forEach(client::leaveRoom);
                    log.info("onDisconnect, userId:{}, roomList:{}", userInfo.getUserId(), rooms);
                } else {
                    log.info("Disconnect");
                    client.disconnect();
                }
                List<String> oldRoomList = client.get(Constants.ROOM);
                if (!CollectionUtils.isEmpty(oldRoomList)) {
                    oldRoomList.stream().filter(StringUtils::isNotBlank).forEach(client::leaveRoom);
                }
                client.disconnect();;

            }
        });

        pushNamespace.addConnectListener(new ConnectListener() {
            @Override
            public void onConnect(SocketIOClient client) {
                UUID sessionId = client.getSessionId();
                JwtUtil.JwtResult userInfo = UserAuthorizationListener.getUserInfo(client.getHandshakeData());
                if (userInfo != null) {
                    Set<String> rooms = client.getAllRooms();
                    rooms.forEach(client::leaveRoom);
                    log.info("onDisconnect, userId:{}, roomList:{}", userInfo.getUserId(), rooms);


                    List<String> roomList = PushUtils.getRoomList(userInfo.getUserId().toString(), null, null);
                    roomList.forEach(client::joinRoom);
                    client.set(Constants.ROOM, roomList);
                } else {
                    log.info("Disconnect");
                    client.disconnect();
                }
            }
        });

    }

    /**
     * 推送消息
     *
     * @param message
     */
    public void push(Message message) {
        Integer userId = message.getUserId();
        String content = message.getContent();
        HashMap<String, Object> map = new HashMap<>();
        map.put("x",10);
        map.put("y",11);
        Object json = JSON.toJSON(map);

        if(userId == null){
            throw new RuntimeException("userId is null");
        }
        List<String> roomList = PushUtils.getRoomList(userId.toString(), null, null);

        Packet packet = new Packet(PacketType.MESSAGE);
        packet.setSubType(PacketType.EVENT);
        packet.setName("hello");
        packet.setData(json);
        packet.setNsp(pushNamespace.getName());
        log.info("parper send message, userId:{}, content:{}", userId, content);
        for (String room : roomList) {
            Iterable<SocketIOClient> roomClients = pushNamespace.getRoomClients(room);
            for (SocketIOClient roomClient : roomClients) {
                roomClient.send(packet);
                log.info("send push message, room:{}", room);

            }
        }
    }

    /**
     * 同步启动服务；
     */
    public void start() {
        try {
            server.start();
        } catch (Exception e) {
            log.error("Push server start failed!", e);
            System.exit(-1);
        }
    }

    /**
     * 停止服务
     */
    public void stop() {
        server.stop();
    }

    public Map<String, Object> getStatus() {
        HashMap<String, Object> status = new HashMap<>();
        status.put("namespace", pushNamespace.getName());   // namespace
        status.put("rooms", pushNamespace.getRooms());
        status.put("clients", pushNamespace.getAllClients().size());
        return status;
    }

}
